package com.eairlv.share.auto.service;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.eairlv.share.auto.entity.bo.ShareSql;
import com.eairlv.share.auto.entity.po.ShareTablePO;
import com.eairlv.share.auto.entity.po.enums.ShareWay;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author eairlv
 * @description
 * @date 14:49 2020/1/16
 */
@Slf4j
public class ShareRule {

    /**
     * 分片规则
     * @param ddl
     * @param shareTablePO
     * @param begin
     * @param excludeTables
     */
    public static List<ShareSql> shareRule(String ddl, ShareTablePO shareTablePO, Date begin, List<String> excludeTables){
        List<ShareSql> ddlSql = new ArrayList<>();
        ShareWay shareTableWay = shareTablePO.getShareTableWay();
        ShareWay sharePartitionWay = shareTablePO.getSharePartitionWay();
        if (shareTableWay.equals(ShareWay.YEAR)){
            shareYearTable(ddlSql, ddl, shareTablePO, sharePartitionWay, excludeTables, begin);
        } else if (shareTableWay.equals(ShareWay.MONTH)){
            shareMonthTable(ddlSql, ddl, shareTablePO, sharePartitionWay, excludeTables, begin);
        } else if (shareTableWay.equals(ShareWay.DAY)){
            shareDayTable(ddlSql, ddl, shareTablePO, sharePartitionWay, excludeTables, begin);
        } else {
            log.error("unknown ShareWay shareTableWay: {}", shareTableWay);
        }
        return ddlSql;
    }

    /**
     * 按年分表
     * @param ddlSql
     * @param ddl
     * @param shareTablePO
     * @param sharePartitionWay
     * @param excludeTables
     * @param begin
     */
    private static void shareYearTable(List<ShareSql> ddlSql, String ddl, ShareTablePO shareTablePO, ShareWay sharePartitionWay,
                                List<String> excludeTables, Date begin){
        for (int i = 0; i < shareTablePO.getShareTableAdvance(); i++) {
            Date nextYear = DateUtil.beginOfYear(DateUtil.offset(begin, DateField.YEAR, i));
            Date nextTwoYear = DateUtil.offset(nextYear, DateField.YEAR, 1);
            Long timeDifference =  nextTwoYear.getTime() - nextYear.getTime();
            String year = DateUtil.format(nextYear, "yyyy");
            String shareTableName = buildShareTableName(shareTablePO.getTemplateTableName(), year);
            if (!excludeTables.contains(shareTableName)){
                StringBuilder stringBuilder = new StringBuilder(ddl.replaceFirst(shareTablePO.getTemplateTableName(),
                        buildShareTableName(shareTablePO.getTemplateTableName(), year)));
                if (sharePartitionWay.equals(ShareWay.MONTH)){
                    // 按年分表、按月分区
                    Integer monthNumber = 12;
                    Integer times = (int)Math.ceil(monthNumber/shareTablePO.getSharePartitionInterval());
                    String partitionSql = buildPartition(shareTablePO.getTemplatePartitionField(),
                            nextYear.getTime(),
                            times,
                            timeDifference / (monthNumber/shareTablePO.getSharePartitionInterval()),
                            nextTwoYear.getTime());
                    stringBuilder.append(partitionSql);
                } else buildPartitionDDL(shareTablePO, sharePartitionWay, stringBuilder, nextYear, nextTwoYear, timeDifference);
                ddlSql.add(ShareSql.builder().tableName(shareTableName).sql(stringBuilder.toString()).build());
            }
        }
    }

    /**
     * 按月分表
     * @param ddlSql
     * @param ddl
     * @param shareTablePO
     * @param sharePartitionWay
     * @param excludeTables
     * @param begin
     */
    private static void shareMonthTable(List<ShareSql> ddlSql, String ddl, ShareTablePO shareTablePO, ShareWay sharePartitionWay,
                                 List<String> excludeTables, Date begin){
        for (int i = 0; i < shareTablePO.getShareTableAdvance(); i++) {
            Date nextMonth = DateUtil.beginOfMonth(DateUtil.offset(begin, DateField.MONTH, i));
            Date nextTwoMonth = DateUtil.offset(nextMonth, DateField.MONTH, 1);
            Long timeDifference =  nextTwoMonth.getTime() - nextMonth.getTime();
            String month = DateUtil.format(nextMonth, "yyyyMM");
            String shareTableName = buildShareTableName(shareTablePO.getTemplateTableName(), month);
            if (!excludeTables.contains(shareTableName)){
                StringBuilder stringBuilder = new StringBuilder(ddl.replaceFirst(shareTablePO.getTemplateTableName(),
                        buildShareTableName(shareTablePO.getTemplateTableName(), month)));
                buildPartitionDDL(shareTablePO, sharePartitionWay, stringBuilder, nextMonth, nextTwoMonth, timeDifference);
                ddlSql.add(ShareSql.builder().tableName(shareTableName).sql(stringBuilder.toString()).build());
            }
        }
    }

    /**
     * 按天分表
     * @param ddlSql
     * @param ddl
     * @param shareTablePO
     * @param sharePartitionWay
     * @param excludeTables
     * @param begin
     */
    private static void shareDayTable(List<ShareSql> ddlSql, String ddl, ShareTablePO shareTablePO, ShareWay sharePartitionWay,
                               List<String> excludeTables, Date begin){
        for (int i = 0; i < shareTablePO.getShareTableAdvance(); i++) {
            Date nextDay = DateUtil.beginOfDay(DateUtil.offset(begin, DateField.DAY_OF_MONTH, i));
            Date nextTwoDay = DateUtil.offset(nextDay, DateField.MONTH, 1);
            Long timeDifference =  nextTwoDay.getTime() - nextDay.getTime();
            String day = DateUtil.format(nextDay, "yyyyMMdd");
            String shareTableName = buildShareTableName(shareTablePO.getTemplateTableName(), day);
            if (!excludeTables.contains(shareTableName)){
                StringBuilder stringBuilder = new StringBuilder(ddl.replaceFirst(shareTablePO.getTemplateTableName(),
                        buildShareTableName(shareTablePO.getTemplateTableName(), day)));
                buildPartitionDDL(shareTablePO, sharePartitionWay, stringBuilder, nextDay, nextTwoDay, timeDifference);
                ddlSql.add(ShareSql.builder().tableName(shareTableName).sql(stringBuilder.toString()).build());
            }
        }
    }

    /**
     * 构建分区DDL
     * @param shareTablePO
     * @param sharePartitionWay
     * @param stringBuilder
     * @param nextMonth
     * @param nextTwoMonth
     * @param timeDifference
     */
    private static void buildPartitionDDL(ShareTablePO shareTablePO, ShareWay sharePartitionWay, StringBuilder stringBuilder, Date nextMonth, Date nextTwoMonth, Long timeDifference) {
        if (sharePartitionWay.equals(ShareWay.DAY)){
            // 按月分表、按天分区
            Integer dayNumber = Math.toIntExact(DateUtil.between(nextMonth, nextTwoMonth, DateUnit.DAY));
            Integer times = (int)Math.ceil(dayNumber/shareTablePO.getSharePartitionInterval());
            String partitionSql = buildPartition(shareTablePO.getTemplatePartitionField(),
                    nextMonth.getTime(),
                    times,
                    timeDifference / (dayNumber/shareTablePO.getSharePartitionInterval()),
                    nextTwoMonth.getTime());
            stringBuilder.append(partitionSql);
        } else if (sharePartitionWay.equals(ShareWay.HOUR)){
            // 按月分表、按小时分区
            Integer hourNumber = Math.toIntExact(DateUtil.between(nextMonth, nextTwoMonth, DateUnit.HOUR));
            Integer times = (int)Math.ceil(hourNumber/shareTablePO.getSharePartitionInterval());
            String partitionSql = buildPartition(shareTablePO.getTemplatePartitionField(),
                    nextMonth.getTime(),
                    times,
                    timeDifference / (hourNumber/shareTablePO.getSharePartitionInterval()),
                    nextTwoMonth.getTime());
            stringBuilder.append(partitionSql);
        } else {
            // 按月分表、不分区
            stringBuilder.append(";");
        }
    }

    /**
     * 构建分片表名
     * @param templateTableName
     * @param share
     * @return
     */
    private static String buildShareTableName(String templateTableName, String share){
        return templateTableName + "_" + share;
    }

    /**
     * 构建分区
     * @param templatePartitionField
     * @param start
     * @param times
     * @param interval
     * @param end
     * @return
     */
    private static String buildPartition(String templatePartitionField, Long start, Integer times, Long interval, Long end){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(" PARTITION BY RANGE (");
        stringBuilder.append(templatePartitionField);
        stringBuilder.append(")(");
        for (int i = 1; i <= times; i++) {
            stringBuilder.append(" PARTITION `p_");
            stringBuilder.append(start);
            stringBuilder.append("_");
            stringBuilder.append(i);
            stringBuilder.append("` VALUES LESS THAN (");
            if (i == times){
                stringBuilder.append(end);
            } else {
                stringBuilder.append(start + interval * i);
            }
            stringBuilder.append(") ENGINE = InnoDB,");
        }
        stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        stringBuilder.append(");");
        return stringBuilder.toString();
    }
}
